// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package file

import (
	"fmt"
	"gitee.com/go-wares/log/base"
	"gitee.com/go-wares/log/conf"
	"os"
	"sync"
	"time"
)

// Adapter
// is a component that print log message on terminal.
type (
	Adapter struct {
		bucket     *base.Bucket[*base.Item]
		formatter  base.LoggerFormatter
		separators map[string]time.Time
		mu         *sync.Mutex
	}
)

// NewAdapter
// creates a new adapter for terminal.
func NewAdapter() *Adapter {
	return (&Adapter{
		formatter: &Formatter{},
	}).init()
}

// GetBucket
// return logger items bucket.
func (o *Adapter) GetBucket() *base.Bucket[*base.Item] {
	return o.bucket
}

// GetFormatter
// returns the logger formatter.
func (o *Adapter) GetFormatter() base.LoggerFormatter {
	return o.formatter
}

// Log
// add logger item into bucket.
func (o *Adapter) Log(item *base.Item) {
	o.bucket.Add(item)
}

// SetFormatter
// set the logger formatter.
func (o *Adapter) SetFormatter(formatter base.LoggerFormatter) {
	o.formatter = formatter
}

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

func (o *Adapter) generate(t time.Time) (path, name string) {
	path = fmt.Sprintf(`%s/%s`, conf.Get().FileAdapter.FilePath, t.Format(conf.Get().FileAdapter.FileSeparator))
	name = fmt.Sprintf(`%s/%s`, path, t.Format(conf.Get().FileAdapter.FileName))
	return
}

// Init
// adapter fields.
func (o *Adapter) init() *Adapter {
	o.separators = make(map[string]time.Time)
	o.mu = new(sync.Mutex)
	o.bucket = base.NewBucket[*base.Item]("file-logger", o.run)
	o.bucket.Start()
	return o
}

// Run
// write log message to files.
func (o *Adapter) run(items []*base.Item) {
	var (
		body       []byte
		data       = make(map[string][][]byte)
		err        error
		name, path string
		ok         bool
	)

	// Release
	// log items when write completed.
	defer func() {
		for _, item := range items {
			item.Release()
		}
	}()

	// Range
	// logger items.
	for _, item := range items {
		// Ignore item
		// if format result is nil.
		if body = o.formatter.Format(item); body == nil {
			continue
		}

		// Generate path and name
		// of the file.
		path, name = o.generate(item.Time)

		// Create map key if first found.
		if _, ok = data[name]; !ok {
			if _, ok = o.separators[name]; !ok {
				if err = os.MkdirAll(path, os.ModePerm); err != nil {
					continue
				}
				o.separators[name] = time.Now()
			}
			data[name] = make([][]byte, 0)
		}

		// Append
		// log message to data.
		data[name] = append(data[name], o.formatter.Format(item))
	}

	// Write to the files.
	for k, vs := range data {
		o.writer(k, vs)
	}
}

func (o *Adapter) writer(name string, data [][]byte) {
	var (
		err  error
		file *os.File
	)

	o.mu.Lock()
	defer func() {
		// Catch
		// runtime panic.
		if r := recover(); r != nil {
			println(fmt.Sprintf(`%v`, r))
		}

		// Write fail.
		if err != nil {
			println(err.Error())
		}

		// Close
		// file resource.
		if file != nil {
			_ = file.Close()
		}

		o.mu.Unlock()
	}()

	// Open
	// file with write only mode.
	if file, err = os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, os.ModePerm); err != nil {
		return
	}

	// Range body and write to file.
	for _, body := range data {
		if _, err = file.WriteString(fmt.Sprintf("%s\n", body)); err != nil {
			break
		}
	}
}
